from model.model import Graph
from .schema import GraphExport
from package.connector.neo4j_db import neo4j_db
from fastapi import HTTPException
from py2neo import Node, Relationship, NodeMatcher, RelationshipMatcher


class GraphView(Graph):

    MaxSize = 1000

    @property
    def nodes(self):
        return self.get_graph_node(self.id)
    
    @property
    def relations(self):
        return self.get_graph_relation(self.id)

    @classmethod
    def _build_relation(cls, graph_id, relation):
        """构建关系"""
        start_node_id = relation.get('start_node_id')
        end_node_id = relation.get('end_node_id')

        start_node = neo4j_db.graph.nodes.match(graph_id, id=start_node_id).first()
        end_node = neo4j_db.graph.nodes.match(graph_id, id=end_node_id).first()
        if not (start_node and end_node):
            return
        relation = Relationship(start_node, end_node, label=graph_id, **relation)
        return relation
    
    @classmethod
    def get_graph_node(cls, graph_id):
        match = NodeMatcher(neo4j_db.graph).match(graph_id).limit(cls.MaxSize).all()
        return match

    @classmethod
    def post_graph_node(cls, graph_id, data):
        match = NodeMatcher(neo4j_db.graph).match(graph_id, **data).first()
        if match:
            raise HTTPException(status_code=400, detail='节点已存在')
        
        node = Node(graph_id, **data)
        neo4j_db.graph.create(node)
        return node 
    
    @classmethod
    def patch_graph_node(cls, graph_id, id, update):
        update.update({'id': id})
        node = neo4j_db.graph.nodes.match(graph_id, id=id).first()
        if not node:
            raise HTTPException(status_code=404, detail='节点不存在')

        node.clear()
        node.update(update)
        neo4j_db.graph.push(node)
        return node

    @classmethod
    def delete_graph_node(cls, graph_id, id):
        sql = f"""match (n:`{graph_id}`{{id: "{id}"}}) detach delete n"""
        resp = neo4j_db.graph.run(sql)
        return {'count': resp.stats().get('nodes_deleted', 0)}
    
    @classmethod
    def get_graph_relation(cls, graph_id):
        relations = RelationshipMatcher(neo4j_db.graph).match(label=graph_id).limit(cls.MaxSize).all()
        return relations

    @classmethod
    def post_graph_relation(cls, graph_id, relation):
        relation = cls._build_relation(graph_id, relation)
        neo4j_db.graph.create(relation)
        return relation
    
    @classmethod
    def patch_graph_relation(cls, graph_id, relation_id, relation):
        relation['id'] = relation_id

        old_relation = RelationshipMatcher(neo4j_db.graph).match(label=graph_id, id=relation_id).first()
        if not old_relation:
            raise HTTPException(status_code=404, detail='关系不存在')
        
        relation = cls._build_relation(graph_id, relation)
        if not relation:
            raise HTTPException(status_code=404, detail='节点不存在')        
        
        s = neo4j_db.graph.begin()
        try:
            s.separate(old_relation)
            s.create(relation)
            neo4j_db.graph.commit(s)
        except Exception as e:
            s.rollback()
        return relation

    @classmethod
    def delete_graph_relation(cls, graph_id, relation_id):
        relation = RelationshipMatcher(neo4j_db.graph).match(label=graph_id, id=relation_id).first()
        neo4j_db.graph.separate(relation)
        return {'count': 1}

    @classmethod
    def get_graph_element(cls, graph_id):
        nodes = cls.get_graph_node(graph_id)
        relations = cls.get_graph_relation(graph_id)
        return {
            'nodes': nodes,
            'relations': relations
        }

    @classmethod
    def delete_graph_element(cls, graph_id):
        resp = neo4j_db.graph.run(f"match (n:`{graph_id}`) detach delete n")
        return {}

    @classmethod
    def export_graph(cls, session, filter_):
        datas = GraphView.get_items(session, filter_=[GraphView.id.in_(body.ids)] if body.ids else None).all()
        out = [schema.GraphExport.from_orm(data) for data in datas]
        return out

    @classmethod
    def _import_graph(cls, session, data):
        graph_id = data['id']

        nodes, relations, graph_id = data.pop(nodes, []), data.pop(relations, [])
        node_map = {node['id']: Node(graph_id, **node) for node in nodes}

        relation_map = {}
        for relation in relations:
            start_node = node_map.get(relation['start_node_id'])
            end_node = node_map.get(relation['end_node_id'])
            if not start_node:
                return {'id': graph_id, 'error': '源节点不存在'}
            if not end_node:
                return {'id': graph_id, 'error': '目标节点不存在'}
            relation_map['relation_id'] = Relationship(start_node, end_node, label=graph_id, **relation)
        
        s = neo4j_db.graph.begin()
        try:
            cls.create_item(session, data, commit=False)  # 创建记录
            [s.create(node) for node in node_map.values]
            [s.create(relation) for node in relation_map.values]
            neo4j_db.graph.commit(s)
            session.commit()
        except Exception as e:
            s.rollback()
            session.rollback()
            return {'id': graph_id, 'error': '数据写入失败'}

    @classmethod
    def import_graph(cls, session, datas, ignore_errors=True):
        ids = [data['id'] for data in datas]  # 导入数据
        ids_ = [item.id for item in session.query(cls).filter(cls.id.in_(ids))]  # 数据库内数据
        if ids_:
            if not ignore_errors:
                raise HTTPException(status_code=400, detail='数据库中包含重复数据')
            
        errors = []
        for data in datas:
            if data['id'] in ids_:
                errors.append({'id': data['id'], 'error': '数据已存在'})
                continue
            
            error = cls._import_graph(session, data)  # 导入数据
            if error:
                errors.append(error)
        return errors
